package start;

import entity.data.DocFile;
import entity.data.DocFolder;
import entity.inte.mycloud.folder.CreateFolderRequest;
import entity.inte.mycloud.folder.CreateFolderResponse;
import entity.inte.mycloud.imports.CreateImportTasksRequest;
import entity.inte.mycloud.imports.JobStatus;
import entity.inte.mycloud.imports.QueryImportTasksResponse;
import entity.inte.mycloud.upload.UploadFileRequest;
import org.apache.log4j.Logger;
import service.FileService;
import service.FolderService;
import service.impl.FileServiceImpl;
import service.impl.FolderServiceImpl;
import session.SessionData;
import util.ImageUploadUtil;
import util.TextEditorUtil;

import javax.swing.*;
import java.awt.*;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class UploadWorker extends SwingWorker<Void, String> {
	private static final Logger LOGGER = Logger.getLogger(UploadWorker.class.getName());
	public static final String FILE_EXTENSION = "md";
	private static final Pattern pattern = Pattern.compile("\\[.*\\]\\((.*)\\)");
	private final FileService fileService = FileServiceImpl.getInstance();
	private final FolderService folderService = FolderServiceImpl.getInstance();

	private static final String[] SPAN_PREFIX = new String[]{
			"<span style=\"color: #2ecc71\">",//上传
			"<span style=\"color: #3498db\">",//导入
			"<span style=\"color: #f1c40f\">",//删除
			"<span style=\"color: #e74c3c\">"//异常
	};

	private MainFrame mainFrame = MainFrame.getInstance();
	private String rootFolder;
	private File dirFile;

	public UploadWorker(String rootFolder, File dirFile) {
		this.rootFolder = rootFolder;
		this.dirFile = dirFile;
	}

	@Override
	protected Void doInBackground() throws Exception {
		// 云空间根目录
		DocFolder root = SessionData.ROOT_FOLDER;
		root.setDir(dirFile);
		root.setCreateFolderResponse(new CreateFolderResponse(rootFolder, null));

		try {
			// 拷贝文件并处理拷贝文件中的本地路径
			processTempFile(dirFile, root);
			// 上传文件并导入文档
			uploadFile(root);
			// 等待所有导入任务完成，不等待完成就无法替换文档中的本地链接
			awaitAllTasksDone(root);
			// 对导入完成的文档进行块更新
			// 删除上传的文件及本地创建的临时文件
			docBlockUpdate(root);

			// 清空集合中数据
			clearData();
		} catch (Exception e) {
			// 失败也要删除掉临时文件
			try {
				recursionDeleteTempFile(root);
			} catch (Exception ex) {
				e.addSuppressed(ex);
			}

			e.printStackTrace();
			publish(SPAN_PREFIX[3] + "导入失败：<br>" + e.getMessage() + "</span>");
			LOGGER.error("文件上传失败", e);
		}
		return null;
	}

	@Override
	protected void process(List<String> chunks) {
		for (String s : chunks) {
			TextEditorUtil.appendAtEnd(mainFrame.getLogPane(), s);
		}
	}

	@Override
	protected void done() {
		LOGGER.info("所有文件导入完成");
		mainFrame.getUploadButton().setEnabled(true);
		mainFrame.setUploadWorker(null);
		JOptionPane.showConfirmDialog(mainFrame, "所有文件导入完成", "成功", JOptionPane.DEFAULT_OPTION, JOptionPane.INFORMATION_MESSAGE);
	}

	private void processTempFile(File dir, DocFolder root) {
		if (dir.isDirectory()) {
			root.setDir(dir);
			processDir(dir, root);
		} else {
			// 处理文件
			ConcurrentLinkedQueue<DocFile> docFiles = root.getFiles();
			if (docFiles == null) {
				docFiles = new ConcurrentLinkedQueue<>();
				root.setFiles(docFiles);
			}
			DocFile docFile = processFile(dir, dir);
			docFile.setParent(null);
			docFiles.add(docFile);
		}

		// 初始化进度条最大值
		SessionData.FILE_COUNT.set(SessionData.FILE_COUNT.get() * 3);
		MainFrame.getInstance().getProgressBar().setMaximum(SessionData.FILE_COUNT.get());
	}

	/**
	 * 处理目录
	 *
	 * @param rootDir 根目录
	 * @param root    目录
	 */
	private void processDir(File rootDir, DocFolder root) {
		File[] files = root.getDir().listFiles(pathname -> pathname.isDirectory() || pathname.getName().endsWith(".md"));
		if (files == null) return;
		Arrays.stream(files)
				.forEach(file -> {
					try {
						if (file.isDirectory()) {
							ConcurrentLinkedQueue<DocFolder> folders = root.getFolders();
							if (folders == null) {
								folders = new ConcurrentLinkedQueue<>();
								root.setFolders(folders);
							}
							// 处理子目录
							DocFolder child = new DocFolder(root, file, null, null, null, null);
							folders.add(child);
							processDir(rootDir, child);
						} else {
							// 处理文件
							ConcurrentLinkedQueue<DocFile> docFiles = root.getFiles();
							if (docFiles == null) {
								docFiles = new ConcurrentLinkedQueue<>();
								root.setFiles(docFiles);
							}
							DocFile docFile = processFile(rootDir, file);
							docFile.setParent(root);
							docFiles.add(docFile);
						}
					} catch (Exception e) {
						e.printStackTrace();
						throw new RuntimeException(e);
					}
				});
	}

	/**
	 * 处理文件
	 *
	 * @param rootDir 根目录
	 * @param file    文件
	 */
	private DocFile processFile(File rootDir, File file) {
		SessionData.FILE_COUNT.incrementAndGet();
		// 临时文件
		File tempFile = new File(file.getParent() + File.separator + SessionData.FILE_PREFIX + file.getName());
		DocFile docFile = new DocFile();
		UploadFileRequest uploadFileRequest = new UploadFileRequest();
		uploadFileRequest.setFile(file);
		docFile.setUploadFileRequest(uploadFileRequest);
		// 按理说应该不会有重名的文件，真重名了就不处理该文件(省事)
		if (!tempFile.exists()) {
			try (BufferedReader br = new BufferedReader(new FileReader(file));
			     BufferedWriter bw = new BufferedWriter(new FileWriter(tempFile))) {
				String line;
				while ((line = br.readLine()) != null) {
					// 处理文件中的本地资源的链接
					Matcher matcher = pattern.matcher(line);
					while (matcher.find()) {
						String url = matcher.group(1);
						// 只要本地链接
						if (url.startsWith("http") || url.startsWith("#")) continue;
						String url2 = url.replaceAll("/", "\\\\");
						String resourceUrl; //本地资源绝对路径

						if (ImageUploadUtil.isImage(url)) {
							// 本地图片上传到图床，修改为图床地址，再上传文件
							if (url2.startsWith("\\")) {
								resourceUrl = rootDir.getAbsolutePath() + url2;
							} else if (new File(url2).isAbsolute()) {
								// 本地绝对路径
								resourceUrl = url2;
							} else {
								resourceUrl = tempFile.getParent() + File.separator + url;
							}
							File resourceFile = new File(resourceUrl);
							// 图片可能不存在
							if (!resourceFile.isFile()) continue;
							String newUrl = ImageUploadUtil.uploadImage(resourceFile, 1);
							// 图片上传图床可能会出问题（头疼）
							while (newUrl == null) {
								newUrl = ImageUploadUtil.uploadImage(resourceFile, 1);
							}
							line = line.replace(url, newUrl);
						} else {
							//文件中新地址，无实际意义，只为了导入时能被飞书识别
							String newUrl = SessionData.URL_PREFIX + url2.replaceAll("\\\\", "").replaceAll(":", "");
							if (url2.startsWith("\\")) {
								// 项目绝对地址
								resourceUrl = rootDir.getAbsolutePath() + url2;
							} else if (new File(url2).isAbsolute()) {
								// 本地绝对路径
								resourceUrl = url2;
							} else {
								resourceUrl = tempFile.getParent() + File.separator + url;
							}
							resourceUrl = resourceUrl.replaceAll("/", "\\\\");
							// 找不到本地文件就不处理
							if (!new File(resourceUrl).exists()) continue;
							line = line.replace(url, newUrl);
							// 将映射添加进map
							Map<String, String> resourceMap = docFile.getResourceMap();
							if (resourceMap == null) {
								// 初始化map
								resourceMap = new HashMap<>();
								docFile.setResourceMap(resourceMap);
							}
							// 存储的是转义后的字符，因为匹配的时候就是转义后的
							resourceMap.put(URLEncoder.encode(newUrl, StandardCharsets.UTF_8), resourceUrl);
						}
					}
					bw.write(line + "\n");
				}
			} catch (IOException e) {
				throw new RuntimeException(e);
			}
		} else {
			// 应该是之前创建的文件，伪造地址还原起来比较麻烦，这里就先不处理了
			// 不过因此也会无法拿到该文件的resourceMap
		}
		return docFile;
	}

	private void uploadFile(DocFolder root) {
		ConcurrentLinkedQueue<DocFile> files = root.getFiles();
		ConcurrentLinkedQueue<DocFolder> folders = root.getFolders();

		if (root.getCreateFolderResponse() == null) {
			// 这里应该再递归下，检查该目录下是否有文件，没文件就不创建目录了

			// 创建目录
			String dirName = root.getDir().getName();
			String parentToken = root.getParent().getCreateFolderResponse().getToken();
			CreateFolderRequest createFolderRequest = new CreateFolderRequest(dirName, parentToken);
			root.setCreateFolderRequest(createFolderRequest);
			root.setCreateFolderResponse(folderService.createFolder(createFolderRequest));
		}

		if (files != null) {
			// 处理文件
			files.forEach(docFile -> {
				// 上传文件
				UploadFileRequest uploadFileRequest = docFile.getUploadFileRequest();
				File file = uploadFileRequest.getFile();
				uploadFileRequest.setFile_name(file.getName());
				// 替换file，替换成拷贝版本
				file = new File(file.getParent() + File.separator + SessionData.FILE_PREFIX + file.getName());
				uploadFileRequest.setFile(file);
				uploadFileRequest.setSize(file.length());
				uploadFileRequest.setParent_node(root.getCreateFolderResponse().getToken());

				docFile.setUploadFileRequest(uploadFileRequest);
				docFile.setUploadFileResponse(fileService.uploadFile(uploadFileRequest, 1));
				publish(SPAN_PREFIX[0] + docFile.getUploadFileRequest().getFile_name() + " 文件上传完成</span>");

				// 创建导入任务
				CreateImportTasksRequest createImportTasksRequest = new CreateImportTasksRequest();
				createImportTasksRequest.setFile_extension(FILE_EXTENSION);
				createImportTasksRequest.setFile_token(docFile.getUploadFileResponse().getFile_token());
				CreateImportTasksRequest.Point point = new CreateImportTasksRequest.Point();
				point.setMount_key(docFile.getUploadFileRequest().getParent_node());
				createImportTasksRequest.setPoint(point);
				docFile.setCreateImportTasksRequest(createImportTasksRequest);
				docFile.setCreateImportTasksResponse(fileService.createImportTasks(createImportTasksRequest, 1));
				EventQueue.invokeLater(() -> mainFrame.getProgressBar().setValue(mainFrame.getProgressBar().getValue() + 1));
			});
		}

		if (folders != null) {
			// 处理目录
			folders.forEach(this::uploadFile);
		}
	}

	private void awaitAllTasksDone(DocFolder root) {
		ConcurrentLinkedQueue<DocFile> files = root.getFiles();
		if (files != null) {
			files //需要等待所有文件导入结束
					.forEach(docFile -> {
						// 查询导入结果
						int jobStatus;
						try {
							while (true) {
								QueryImportTasksResponse queryImportTasksResponse = fileService.queryImportTasks(docFile.getCreateImportTasksResponse(), 1);
								jobStatus = queryImportTasksResponse.getResult().getJob_status();
								if (jobStatus != 1 && jobStatus != 2) {
									docFile.setQueryImportTasksResponse(queryImportTasksResponse);
									break;
								}
								TimeUnit.MILLISECONDS.sleep(200);
							}

							if (jobStatus == 0) {
								publish(SPAN_PREFIX[1] + docFile.getUploadFileRequest().getFile_name() + " 文件导入完成</span>");
							} else {
								// 失败
								String warn = docFile.getUploadFileRequest().getFile_name() + " 导入失败\n" + JobStatus.getDescription(jobStatus);
								LOGGER.warn(warn);
								publish(SPAN_PREFIX[3] + warn + "</span>");
							}

							// 更新map映射
							SessionData.DOC_FILE_MAP.put(docFile.getUploadFileRequest().getFile().getAbsolutePath().replaceAll(SessionData.FILE_PREFIX, ""),
									docFile);
							EventQueue.invokeLater(() -> mainFrame.getProgressBar().setValue(mainFrame.getProgressBar().getValue() + 1));
						} catch (InterruptedException e) {
							throw new RuntimeException(e);
						}

					});
		}

		ConcurrentLinkedQueue<DocFolder> folders = root.getFolders();
		if (folders != null) {
			// 处理目录
			folders.forEach(this::awaitAllTasksDone);
		}
	}

	/**
	 * 对导入完成的文档进行块更新
	 */
	private void docBlockUpdate(DocFolder root) throws InterruptedException {
		ConcurrentLinkedQueue<DocFile> files = root.getFiles();
		if (files != null) {
			files.forEach(docFile -> {
				// 成功，批量更新块
				fileService.batchUpdateProcess(docFile, 1);
				// 删除本地临时文件和上传文件
				deleteTempFile(docFile);

				EventQueue.invokeLater(() -> mainFrame.getProgressBar().setValue(mainFrame.getProgressBar().getValue() + 1));
			});
		}

		ConcurrentLinkedQueue<DocFolder> folders = root.getFolders();
		if (folders != null) {
			// 处理目录
			for (DocFolder folder : folders) {
				docBlockUpdate(folder);
			}
		}
	}

	private void recursionDeleteTempFile(DocFolder root) {
		Optional.ofNullable(root.getFiles())
				.stream()
				.flatMap(Collection::stream)
				.forEach(this::deleteTempFile);

		Optional.ofNullable(root.getFolders())
				.stream()
				.flatMap(Collection::stream)
				.forEach(this::recursionDeleteTempFile);
	}

	private void deleteTempFile(DocFile docFile) {
		RuntimeException exception = null;
		try {
			docFile.setDeleteFileResponse(fileService.deleteDoc(docFile.getUploadFileResponse(), 1));
		} catch (RuntimeException e) {
			exception = e;
		}
		delLocaleFile(docFile.getUploadFileRequest().getFile());
		publish(SPAN_PREFIX[2] + docFile.getUploadFileRequest().getFile_name() + " 临时文件删除完成</span>");
		if (exception != null) throw exception;
	}

	/**
	 * 删除本地文件，需要确保关于该文件的inputStream都已关闭
	 */
	private void delLocaleFile(File file) {
		file.delete();
	}

	/**
	 * 清空集合中数据
	 */
	private void clearData() {
		SessionData.FILE_COUNT.set(0);

		SessionData.ROOT_FOLDER.setFolders(null);
		SessionData.ROOT_FOLDER.setCreateFolderRequest(null);
		SessionData.ROOT_FOLDER.setDir(null);
		SessionData.ROOT_FOLDER.setCreateFolderResponse(null);
		SessionData.ROOT_FOLDER.setFiles(null);
		SessionData.DOC_FILE_MAP.clear();
	}

	public void processException(String message, Exception e) {
		e.printStackTrace();
		LOGGER.error(message == null ? e.getMessage() : message, e);
		TextEditorUtil.appendAtEnd(mainFrame.getLogPane(), (message == null ? "" : message + "\n") + e.getMessage());
	}
}
